package com.example.ramcache.orm.impl;

import com.example.ramcache.PerPersist;
import com.example.ramcache.orm.Paging;
import com.example.ramcache.orm.Querier;
import org.hibernate.Criteria;
import org.hibernate.HibernateException;
import org.hibernate.Session;
import org.hibernate.internal.StatelessSessionImpl;
import org.hibernate.query.Query;

import javax.annotation.PostConstruct;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;
import java.util.*;
import java.util.Map.Entry;

/**
 * @author frank
 * {@link Querier} 的 Hibernate 实现
 */
@SuppressWarnings({"unchecked", "rawtypes"})
public class HibernateQuerier extends HibernateHelper implements Querier {
    @PostConstruct
    public void initializer() {
        super.init();
    }

    @Override
    public <T> List<T> all(final Class<T> clz) {
        List<T> ret = execute(new Callback<List<T>>() {
            @Override
            public List<T> call(StatelessSessionImpl session) throws HibernateException {
                Criteria criteria = session.createCriteria(clz);
                criteria.setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY);
                return criteria.list();
            }
        });
        doAfterLoad(clz, ret);
        return ret;
    }

    @Override
    public <T> List<T> list(Class<T> clz, final String queryname, final Object... params) {

        List<T> ret = execute(new Callback<List<T>>() {
            @Override
            public List<T> call(StatelessSessionImpl session) throws HibernateException {
                Query queryObject = session.getNamedQuery(queryname);
                queryObject.getParameterMetadata().getNamedParameterNames();
                if (params != null) {
                    for (int i = 0; i < params.length; i++) {
                        queryObject.setParameter(i, params[i]);
                    }
                }
                return queryObject.list();
            }
        });
        doAfterLoad(clz, ret);
        return ret;
    }

    @Override
    public <T> List<T> list(Class<T> clz, final String queryname, final Map<String, Object> params) {
        List<T> ret = execute(new Callback<List<T>>() {
            @Override
            public List<T> call(StatelessSessionImpl session) throws HibernateException {
                Query queryObject = session.getNamedQuery(queryname);
                if (params != null) {
                    for (String key : queryObject.getParameterMetadata().getNamedParameterNames()) {
                        Object param = params.get(key);
                        if (param instanceof Collection) {
                            queryObject.setParameterList(key, (Collection) param);
                        } else {
                            queryObject.setParameter(key, param);
                        }
                    }
                }
                return queryObject.list();
            }
        });
        doAfterLoad(clz, ret);
        return ret;
    }

    @Override
    public <E> List<E> list(Class entityClz, Class<E> retClz, String queryname, Object... params) {
        return list(retClz, queryname, params);
    }

    @Override
    public <T> T unique(Class<T> clz, final String queryname, final Object... params) {
        T ret = execute(new Callback<T>() {
            @Override
            public T call(StatelessSessionImpl session) throws HibernateException {
                Query query = session.getNamedQuery(queryname);
                if (params != null) {
                    for (int i = 0; i < params.length; i++) {
                        query.setParameter(i, params[i]);
                    }
                }
                return (T) query.uniqueResult();
            }
        });
        if (ret instanceof PerPersist) {
            ((PerPersist) ret).afterLoad();
        }
        return ret;
    }

    @Override
    public <E> E unique(Class entityClz, Class<E> retClz, String queryname, Object... params) {
        return unique(retClz, queryname, params);
    }

    @Override
    public <T> List<T> paging(final Class<T> clz, final String queryname, final Paging paging, final Object... params) {
        List<T> ret = execute(new Callback<List<T>>() {
            @Override
            public List<T> call(StatelessSessionImpl session) throws HibernateException {
                Query query = session.getNamedQuery(queryname);
                if (params != null) {
                    for (int i = 0; i < params.length; i++) {
                        query.setParameter(i, params[i]);
                    }
                }
                query.setFirstResult(paging.getFirst());
                query.setMaxResults(paging.getSize());
                return query.list();
            }
        });
        doAfterLoad(clz, ret);
        return ret;
    }

    @Override
    public <E> List<E> paging(Class entityClz, Class<E> retClz, String queryname, Paging paging, Object... params) {
        return paging(retClz, queryname, paging, params);
    }

    @Override
    public int execute(Class entityClz, final String queryname, final Object... params) {

        return execute(new Callback<Integer>() {
            @Override
            public Integer call(StatelessSessionImpl session) {
                Query query = session.getNamedQuery(queryname);
                Set<String> names = query.getParameterMetadata().getNamedParameterNames();
                if (names == null || names.size() == 0) {
                    for (int i = 0; i < params.length; i++) {
                        query.setParameter(i, params[i]);
                    }
                } else {
                    Map<String, Object> kv = (Map<String, Object>) params[0];
                    for (Entry<String, Object> entry : kv.entrySet()) {
                        String name = entry.getKey();
                        Object value = entry.getValue();
                        if (value instanceof Collection) {
                            query.setParameterList(name, (Collection) value);
                        } else {
                            query.setParameter(name, value);
                        }
                    }
                }
                return query.executeUpdate();
            }
        });
    }

    // 内部方法

    /**
     * 加载的后处理方法
     *
     * @param clz
     * @param ret
     */
    private <T> void doAfterLoad(Class<T> clz, List<T> ret) {
        if (PerPersist.class.isAssignableFrom(clz)) {
            for (T t : ret) {
                ((PerPersist) t).afterLoad();
            }
        }
    }

    @Override
    public List<?> query(Class entity, int pageIndex, int pageSize, Map<String, Object> keyValue, boolean and) {
        try (Session session = this.getSessionFactory().openSession()) {
            int start = (pageIndex - 1) * pageSize;
            CriteriaBuilder crb = session.getCriteriaBuilder();
            CriteriaQuery<?> crq = crb.createQuery(entity);
            Root root = crq.from(entity);
            crq.select(root);
            List<Predicate> predicateList = new ArrayList();
            if (keyValue != null && !keyValue.isEmpty()) {
                keyValue.forEach((key, value) -> {
                    predicateList.add(crb.equal(root.get(key), value));
                });
                if (and) {
                    crq.where(crb.and(predicateList.toArray(new Predicate[]{})));
                } else {
                    crq.where(crb.or(predicateList.toArray(new Predicate[]{})));
                }
            }
            Query<?> query = session.createQuery(crq);
            query.setFirstResult(start);
            query.setMaxResults(pageSize);
            List<?> list = query.getResultList();
            doAfterLoad(entity, list);
            return list;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
}